package com.tcm.service.impl;

import cn.hutool.core.util.IdUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.tcm.common.enums.CachePrefix;
import com.tcm.config.AppProperties;
import com.tcm.entity.Group;
import com.tcm.entity.GroupUser;
import com.tcm.entity.LearningTime;
import com.tcm.entity.User;
import com.tcm.entity.bo.GroupInvitationBo;
import com.tcm.entity.bo.UserCacheBo;
import com.tcm.entity.dto.GroupDto;
import com.tcm.entity.dto.param.GroupCreateParam;
import com.tcm.entity.vo.GroupInfoResp;
import com.tcm.entity.vo.GroupListDto;
import com.tcm.entity.vo.GroupListResp;
import com.tcm.exception.DatabaseOperationException;
import com.tcm.exception.EntityInvalidException;
import com.tcm.mapper.GroupMapper;
import com.tcm.mapper.GroupUserMapper;
import com.tcm.mapper.TeacherMapper;
import com.tcm.service.*;
import com.tcm.utils.RedisUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * @author Guqier
 * @description 针对表【ts_group(学习小组主表)】的数据库操作Service实现
 * @createDate 2023-09-11 21:18:00
 */
@Service
public class GroupServiceImpl extends ServiceImpl<GroupMapper, Group>
        implements GroupService {

    @Autowired
    private GroupUserMapper groupUserMapper;

    @Autowired
    private RedisUtil redisUtil;

    @Autowired
    private UserService userService;

    @Autowired
    private ImageService imageService;

    @Autowired
    private LearningTimeService learningTimeService;

    @Autowired
    private ScoreService scoreService;

    @Autowired
    private TeacherMapper teacherMapper;

    @Autowired
    private RoleService roleService;

    @Autowired
    private NotificationService notificationService;

    /**
     * 创建小组
     * @param param      小组创建参数
     * @param currUserId 当前登录用户ID
     * @return 创建结果，true为成功，false为小组已到最大数量
     */
    @Override
    @Transactional
    public boolean createGroup(GroupCreateParam param, Long currUserId) {
        // 获取用户信息
        UserCacheBo userCacheBo = redisUtil.getCacheObject(CachePrefix.PC_USER_TOKEN.getCacheKey(currUserId.toString()));
        // 查询当前用户已经加入小组数量
        Integer count = groupUserMapper.selectCount(new LambdaQueryWrapper<GroupUser>().eq(GroupUser::getAccount, userCacheBo.getAccount()));
        // 查询加入小组是否已到最大小组数
        if (Objects.equals(count, AppProperties.GROUP_JOIN_MAX)) {
            return false;
        }

        // 构建实体类
        Group group = new Group();
        group.setName(StringUtils.hasText(param.getGroupName()) ? param.getGroupName() : userCacheBo.getName() + "的小组");
        group.setCreateUser(currUserId);
        group.setUpdateUser(currUserId);
        if (!this.save(group)) {
            throw new DatabaseOperationException("小组创建失败");
        }

        // 创建小组用户关系
        GroupUser groupUser = new GroupUser();
        groupUser.setAccount(userCacheBo.getAccount());
        groupUser.setGroupId(group.getId());
        groupUser.setIsCreator(1);
        groupUser.setCreateUser(currUserId);
        groupUser.setUpdateUser(currUserId);
        if (groupUserMapper.insert(groupUser) != 1) {
            throw new DatabaseOperationException("加入小组保存失败");
        }
        return true;
    }

    /**
     * 获取学习小组列表
     * @param account 当前登录用户账号
     * @return 学习小组列表
     */
    @Override
    public List<GroupListResp> getGroupList(String account) {
        // 查询当前用户所在小组
        List<GroupUser> groupUsers = groupUserMapper.selectList(new LambdaQueryWrapper<GroupUser>().eq(GroupUser::getAccount, account));
        List<Long> ids = groupUsers.stream().map(GroupUser::getGroupId).collect(Collectors.toList());
        List<Group> groups = this.list(new LambdaQueryWrapper<Group>()
                .in(!CollectionUtils.isEmpty(ids), Group::getId, ids)
                .apply(CollectionUtils.isEmpty(ids), "1 != 1")
                .orderByDesc(Group::getCreateTime));

        // 构造结果集
        return groups.stream().map(group -> {
            GroupListResp resp = new GroupListResp();
            GroupUser creator = groupUserMapper.selectOne(new LambdaQueryWrapper<GroupUser>().eq(GroupUser::getGroupId, group.getId()).eq(GroupUser::getIsCreator, 1));
            User user = userService.getUserByAccount(creator.getAccount());
            Optional.ofNullable(user.getAvatar()).ifPresent(id -> resp.setAvatarUrl(imageService.getImageById(id).getRemoteFileUrl()));
            resp.setId(group.getId());
            resp.setGroupName(group.getName());
            resp.setCreateTime(group.getCreateTime());
            resp.setCount(groupUserMapper.selectCount(new LambdaQueryWrapper<GroupUser>().eq(GroupUser::getGroupId, group.getId())));
            groupUsers.stream().filter(groupUser -> groupUser.getGroupId().equals(group.getId())).findFirst().ifPresent(groupUser -> resp.setIsCreator(groupUser.getIsCreator()));
            return resp;
        }).collect(Collectors.toList());
    }


    private String createInvitationCode(Long groupId, String groupName) {
        String code = IdUtil.simpleUUID();
        GroupInvitationBo bo = new GroupInvitationBo();
        bo.setGroupId(groupId);
        bo.setGroupName(groupName);
        redisUtil.setCacheObject(CachePrefix.GROUP_INVITATION_CODE.getCacheKey(code), bo, 5, TimeUnit.MINUTES);// 邀请码时效五分钟
        return code;
    }

    /**
     * 获取邀请小组信息
     * @param code 邀请码
     * @return 小组信息
     */
    @Override
    public GroupDto getInvitationInfo(String code) {
        GroupInvitationBo bo = redisUtil.getCacheObject(CachePrefix.GROUP_INVITATION_CODE.getCacheKey(code));
        if (Objects.isNull(bo)) {
            return null;
        }
        GroupDto dto = new GroupDto();
        dto.setId(bo.getGroupId());
        dto.setName(bo.getGroupName());
        dto.setCount(groupUserMapper.selectCount(new LambdaQueryWrapper<GroupUser>().eq(GroupUser::getGroupId, dto.getId())));
        return dto;
    }

    /**
     * 获取小组邀请码
     * @param groupId 小组ID
     * @return 邀请码
     */
    @Override
    public Map<String, String> getGroupInvitationCode(Long groupId) {
        // 查询小组信息
        Group group = this.getById(groupId);
        if (Objects.isNull(group)) {
            return null;
        }
        String code = createInvitationCode(group.getId(), group.getName());
        Map<String, String> map = new HashMap<>();
        map.put("code", code);
        return map;
    }

    /**
     * 加入小组
     * @param code       邀请码
     * @param currUserId 当前用户ID
     * @return 加入结果
     */
    @Override
    @Transactional
    public boolean joinGroup(String code, Long currUserId) {
        // 获取用户信息
        UserCacheBo userCacheBo = redisUtil.getCacheObject(CachePrefix.PC_USER_TOKEN.getCacheKey(currUserId.toString()));
        // 获取邀请信息
        GroupDto dto = this.getInvitationInfo(code);
        if (Objects.isNull(dto)) {
            return false;
        }
        // 获取小组信息
        Group group = this.getById(dto.getId());
        if (Objects.isNull(group)) {
            throw new EntityInvalidException("小组不存在或已解散");
        }
        List<GroupUser> groupUsers = groupUserMapper.selectList(new LambdaQueryWrapper<GroupUser>().eq(GroupUser::getGroupId, group.getId()));
        Optional<GroupUser> optional = groupUsers.stream().filter(groupUser -> groupUser.getAccount().equals(userCacheBo.getAccount())).findFirst();
        if (optional.isPresent()) {
            throw new EntityInvalidException("已经加入该小组");
        }
        // 保存成员信息
        if (!addGroupUser(group, userCacheBo.getAccount(), currUserId)) {
            throw new DatabaseOperationException("加入小组失败");
        }
        groupUsers.stream().filter(groupUser -> groupUser.getIsCreator().equals(1)).findFirst().ifPresent(groupUser -> {
            // 添加通知
            if (!notificationService.addNotificationForStudent("加入小组通知",
                    String.format("您的小组【%s】有新成员加入", group.getName()),
                    AppProperties.NotificationType.IMPORTANT, 0, groupUser.getAccount(), currUserId)) {
                throw new DatabaseOperationException("通知发送失败");
            }
        });


        return true;
    }

    /**
     * 添加小组成员
     * @param group      小组对象
     * @param account    当前用户账号
     * @param currUserId 当前用户ID
     * @return 添加结果
     */
    private boolean addGroupUser(Group group, String account, Long currUserId) {
        GroupUser groupUser = new GroupUser();
        groupUser.setIsCreator(0);
        groupUser.setGroupId(group.getId());
        groupUser.setAccount(account);
        groupUser.setCreateUser(currUserId);
        groupUser.setUpdateUser(currUserId);
        return groupUserMapper.insert(groupUser) == 1;
    }

    /**
     * 获取小组信息学习情况
     * @param groupId 小组ID
     * @param range   时间范围
     * @return 小组学习情况
     */
    @Override
    public GroupInfoResp getGroupInfo(Long groupId, String range) {
        Map<String, Integer> dateRangeMap = AppProperties.DATE_RANGE_MAP;
        Integer day = dateRangeMap.get(range);
        if (Objects.isNull(day)) {
            return null;
        }
        // 获取小组信息
        Group group = this.getById(groupId);
        if (Objects.isNull(group)) {
            throw new EntityInvalidException("小组不存在或已解散");
        }
        // 获取小组成员信息
        List<GroupUser> groupUsers = groupUserMapper.selectList(new LambdaQueryWrapper<GroupUser>().eq(GroupUser::getGroupId, group.getId()));

        // 封装结果集
        GroupInfoResp groupInfoResp = new GroupInfoResp();
        groupInfoResp.setId(group.getId());
        groupInfoResp.setName(group.getName());
        groupInfoResp.setMembers(groupUsers.stream().map(groupUser -> {
            GroupInfoResp.GroupMember member = new GroupInfoResp.GroupMember();
            User user = userService.getUserByAccount(groupUser.getAccount());
            member.setAccount(user.getAccount());
            member.setNickname(user.getNickname());
            // 获取用户头像
            if (Objects.nonNull(user.getAvatar())) {
                member.setAvatarUrl(imageService.getImageById(user.getAvatar()).getRemoteFileUrl());
            }
            member.setIsCreator(groupUser.getIsCreator());
            // 获取学习时长
            List<LearningTime> learningTimeList = learningTimeService.getLearningTimeListByRange(groupUser.getAccount(), day);
            member.setLeaningTime(learningTimeList.stream().mapToInt(LearningTime::getTime).sum() / 60);
            // 获取积分
            member.setScore(scoreService.getObtainScoreByRange(groupUser.getAccount(), day));
            return member;
        }).sorted(((o1, o2) -> o2.getScore().compareTo(o1.getScore()))).collect(Collectors.toList()));
        return groupInfoResp;
    }

    /**
     * 删除小组
     * @param id         小组ID
     * @param currUserId 当前登录用户ID
     * @return 删除结果，true为成功，false为小组不存在
     */
    @Override
    @Transactional
    public boolean removeGroup(Long id, Long currUserId) {
        // 获取用户信息
        UserCacheBo userCacheBo = redisUtil.getCacheObject(CachePrefix.PC_USER_TOKEN.getCacheKey(currUserId.toString()));
        // 查询小组是否存在
        Group group = this.getById(id);
        if (Objects.isNull(group)) {
            return false;
        }
        // 查询是否是组长
        GroupUser groupUser = groupUserMapper.selectOne(new LambdaQueryWrapper<GroupUser>().eq(GroupUser::getGroupId, group.getId()).eq(GroupUser::getAccount, userCacheBo.getAccount()));
        if (groupUser.getIsCreator() != 1) {
            // 如果不是组长则报错
            throw new EntityInvalidException("只允许组长解散小组");
        }
        // 删除小组信息
        if (!this.removeById(id)) {
            throw new DatabaseOperationException("数据库操作失败");
        }
        List<GroupUser> groupUsers = groupUserMapper.selectList(new LambdaQueryWrapper<GroupUser>().eq(GroupUser::getGroupId, group.getId()));
        int delete = groupUserMapper.delete(new LambdaQueryWrapper<GroupUser>().eq(GroupUser::getGroupId, group.getId()));
        if (groupUsers.size() != delete) {
            throw new DatabaseOperationException("数据库操作失败");
        }

        // 过滤组长
        groupUsers = groupUsers.stream().filter(groupUser1 -> groupUser1.getIsCreator().equals(0)).collect(Collectors.toList());
        // 添加通知
        for (GroupUser user : groupUsers) {
            if (!notificationService.addNotificationForStudent("解散小组通知",
                    String.format("您所在的小组【%s】已被解散", group.getName()),
                    AppProperties.NotificationType.IMPORTANT, 0, user.getAccount(), currUserId)) {
                throw new DatabaseOperationException("通知发送失败");
            }
        }

        return true;
    }

    /**
     * 退出小组
     * @param id         小组ID
     * @param currUserId 当前用户ID
     * @return 退出结果
     */
    @Override
    @Transactional
    public boolean exitGroup(Long id, Long currUserId) {
        // 获取用户信息
        UserCacheBo userCacheBo = redisUtil.getCacheObject(CachePrefix.PC_USER_TOKEN.getCacheKey(currUserId.toString()));
        // 查询小组是否存在
        Group group = this.getById(id);
        if (Objects.isNull(group)) {
            return false;
        }
        // 查询是否是组长
        GroupUser groupUser = groupUserMapper.selectOne(new LambdaQueryWrapper<GroupUser>().eq(GroupUser::getGroupId, group.getId()).eq(GroupUser::getAccount, userCacheBo.getAccount()));
        if (groupUser.getIsCreator() == 1) {
            // 如果是组长则报错
            throw new EntityInvalidException("组长不能退出小组");
        }

        // 添加通知
        if (!notificationService.addNotificationForStudent("退出小组通知",
                String.format("您的小组【%s】有成员退出", group.getName()),
                AppProperties.NotificationType.IMPORTANT, 0, groupUserMapper.selectOne(new LambdaQueryWrapper<GroupUser>().eq(GroupUser::getGroupId, group.getId()).eq(GroupUser::getIsCreator, 1)).getAccount(), currUserId)) {
            throw new DatabaseOperationException("通知发送失败");
        }

        // 删除组员信息
        return groupUserMapper.deleteById(groupUser.getId()) == 1;
    }

    /**
     * 修改小组名称
     * @param name       小组新名称
     * @param id         小组ID
     * @param currUserId 当前用户ID
     * @return 修改结果
     */
    @Override
    @Transactional
    public boolean renameGroup(String name, Long id, Long currUserId) {
        // 获取用户信息
        UserCacheBo userCacheBo = redisUtil.getCacheObject(CachePrefix.PC_USER_TOKEN.getCacheKey(currUserId.toString()));
        // 查询小组是否存在
        Group group = this.getById(id);
        if (Objects.isNull(group)) {
            return false;
        }
        // 查询是否是组长
        GroupUser groupUser = groupUserMapper.selectOne(new LambdaQueryWrapper<GroupUser>().eq(GroupUser::getGroupId, group.getId()).eq(GroupUser::getAccount, userCacheBo.getAccount()));
        if (groupUser.getIsCreator() != 1) {
            // 如果不是组长则报错
            throw new EntityInvalidException("只允许组长重命名小组");
        }
        String oldName = group.getName();
        group.setName(name);
        group.setUpdateUser(currUserId);

        // 添加通知
        List<GroupUser> groupUsers = groupUserMapper.selectList(new LambdaQueryWrapper<GroupUser>().eq(GroupUser::getGroupId, group.getId()).eq(GroupUser::getIsCreator, 0));
        for (GroupUser user : groupUsers) {
            if (!notificationService.addNotificationForStudent("小组名称修改通知",
                    String.format("您所在的小组【%s】已被组长修改名称，新名称为【%s】", oldName, name),
                    AppProperties.NotificationType.IMPORTANT, 0, user.getAccount(), currUserId)) {
                throw new DatabaseOperationException("通知发送失败");
            }
        }

        return this.updateById(group);
    }

    @Override
    public List<GroupListDto> getGroupListByTeacherAccount(String currAccount, Long currUserId) {
        // 如果是管理员则返回全部小组信息，否则返回所教班级信息
        return roleService.isAdministrator(currUserId) ? this.getBaseMapper().selectGroupList() : this.getBaseMapper().selectGroupListByTeacherAccount(currAccount);
    }

    @Override
    public GroupInfoResp getGroupInfoByTeacherAccount(Long groupId, String range, String account) {
        List<String> accounts = teacherMapper.selectStudentAccountByTeacherAccount(account);
        GroupInfoResp groupInfo = this.getGroupInfo(groupId, range);
        List<GroupInfoResp.GroupMember> members = groupInfo.getMembers().stream().peek(groupMember -> {
            if (accounts.contains(groupMember.getAccount())) {
                groupMember.setIsMyStudent(true);
            } else {
                groupMember.setIsMyStudent(false);
            }
        }).collect(Collectors.toList());
        groupInfo.setMembers(members);
        return groupInfo;
    }

}
